/// @file MainApp.cpp
/// @brief the main gui application class, extended from the
/// MainWindow_ui and holds all the UI and our main gl window

#include "MainApp.h"
#include "Configuration.h"

#include "ui_MainWindow.h"

MainApp::MainApp
                (
                    QWidget* _parent
                ):
                QMainWindow(_parent),
                m_ui(new Ui::MainWindow)
{
    // setup the user interface
    m_ui->setupUi(this);

    //create a new instance of the world and give it an initial aspect ratio
    m_world = new World(this);

    m_ui->s_mainGridLayout->addWidget(m_world, 0, 0, 4, 2);

    //setup signals/slots
    setupConnections();
}

MainApp::~MainApp()
{
    delete m_ui;
}

void MainApp::setupConnections()
{
    //read values from world managers and set up control initial values here

    //TAB : WALL
    Wall* w = m_world->getWall();

    //enable/disable wall
    m_ui->wall_GroupBox->setChecked(w->getEnableWall());

    //set bounds
    m_ui->spin_wall_dimensions_x->setValue(w->getBounds().m_x);
    m_ui->spin_wall_dimensions_y->setValue(w->getBounds().m_y);
    m_ui->spin_wall_dimensions_z->setValue(w->getBounds().m_z);

    //enable/disable hard/soft limiting
    m_ui->rdo_wall_limiting_hard->setChecked(w->getHardWall());
    setSliderWallSsoftWeightState(!w->getHardWall());

    //slider soft weight value
    m_ui->sld_wall_soft_weight->setValue(toSliderValue(0.01, 1, w->getSoftLimitWeight()));


    //TAB : FLOCK
    FlockManager* fm = m_world->getFlockManager();

    //set number of boids initially to be created
    m_ui->spin_flock_numberOfBoids->setValue(fm->getInitialNumberOfBoids());

    //toggle trace boids & set value and max value
    m_ui->traceBoidAndNeighbour_GroupBox->setChecked(fm->getEnableBoidNeighbourScreening());
    m_ui->spin_flock_traceBoidAndNeighbours_boidNo->setValue(fm->getNeighbourUnderScreen());
    m_ui->spin_flock_traceBoidAndNeighbours_boidNo->setMaximum(fm->getNumberOfBoids() - 1);


    //TAB : RULES -> BASIC

    //normalise rules
    m_ui->chk_rules_normaliseVelocities->setChecked(fm->getNormaliseRules());

    //cohesion
    m_ui->cohesion_GroupBox->setChecked(fm->getEnableCohesion());

    //slider cohesion weight value
    m_ui->sld_rules_cohesion_weight->setValue(toSliderValue(0.01, 1, fm->getCohesionWeight()));

    //separation
    m_ui->separation_GroupBox->setChecked(fm->getEnableSeparation());

    //slider separation weight & distance value
    m_ui->sld_rules_separation_weight->setValue(toSliderValue(0.01, 1, fm->getSeparationWeight()));
    m_ui->sld_rules_separation_threshold->setValue(toSliderValue(2, 20, fm->getSeparationDistance()));

    //alignment
    m_ui->alignment_GroupBox->setChecked(fm->getEnableAlignment());

    //slider alignment weight
    m_ui->sld_rules_alignment_weight->setValue(toSliderValue(0.01, 1, fm->getAlignmentWeight()));


    //TAB : RULES -> ADDITIONAL

    //tend toward
    m_ui->tendToward_GroupBox->setChecked(fm->getEnableTendToward());

    //set tend toward position
    m_ui->dspin_rules_tendTowards_position_x->setValue(fm->getTendToward().m_x);
    m_ui->dspin_rules_tendTowards_position_y->setValue(fm->getTendToward().m_y);
    m_ui->dspin_rules_tendTowards_position_z->setValue(fm->getTendToward().m_z);

    //slider tend toward weight value
    m_ui->sld_rules_tendTowards_weight->setValue(toSliderValue(0.01, 1, fm->getTendTowardWeight()));

    //external force
    m_ui->externalForce_GroupBox->setChecked(fm->getEnableExternalForce());

    //set external force
    m_ui->dspin_rules_externalForce_force_x->setValue(fm->getExternalForce().m_x);
    m_ui->dspin_rules_externalForce_force_y->setValue(fm->getExternalForce().m_y);
    m_ui->dspin_rules_externalForce_force_z->setValue(fm->getExternalForce().m_z);

    //slider external force weight value
    m_ui->sld_rules_externalForce_weight->setValue(toSliderValue(0.01, 1, fm->getExternalForceWeight()));

    //TAB : RULES -> OBSTACLE
    ObstacleManager* om = m_world->getObstacleManager();

    //toggle obstacle states
    m_ui->obstacle_GroupBox->setChecked(om->getEnableObstacle());
    m_ui->chk_rules_obstacle_wireframe->setChecked(om->getDrawWireframe());
    m_ui->chk_rules_obstacle_drawInfluenceSphere->setChecked(om->getDrawInfluenceSphere());

    //toggle obstacle avoidance
    m_ui->obstacleAvoidance_GroupBox->setChecked(fm->getEnableObstacleAvoidance());

    //slider obstacle avoidance weight
    m_ui->sld_rules_obstacleAvoidance_weight->setValue(toSliderValue(0.01, 1, fm->getObstacleAvoidanceWeight()));


    //TAB : BOID

    //model details -> name, wireframe, bounding sphere and scale

    //m_ui->cmb_boid_model->setSelected(fm->getBoidModel());
    int indexModel = m_ui->cmb_boid_model->findText(QString(fm->getBoidModel().c_str()));
    m_ui->cmb_boid_model->setCurrentIndex(indexModel);

    m_ui->chk_boid_model_wireframe->setChecked(fm->getWireframeBoids());
    m_ui->chk_boid_drawBoundingSphere->setChecked(fm->getDrawBoidBoundingSphere());
    m_ui->sld_boid_scale->setValue(toSliderValue(0.5, 5, fm->getBoidScaleFactor()));

    //color spinners
    m_ui->dspin_boid_color_r->setValue(fm->getBoidColor().m_r);
    m_ui->dspin_boid_color_g->setValue(fm->getBoidColor().m_g);
    m_ui->dspin_boid_color_b->setValue(fm->getBoidColor().m_b);

    //random color checker
    m_ui->chk_boid_color_random->setChecked(fm->getEnableRandomBoidColor());
    setBoiColorSpinnerEnablement(!fm->getEnableRandomBoidColor());

    //shader
    int indexShader = m_ui->cmb_boid_shader->findText(QString(fm->getBoidShader().c_str()));
    m_ui->cmb_boid_shader->setCurrentIndex(indexShader);


    //speed sliders
    m_ui->sld_boid_speed_min->setValue(toSliderValue(0.01, 1, fm->getMinSpeed()));
    m_ui->sld_boid_speed_max->setValue(toSliderValue(1, 100, fm->getMaxSpeed()));

    //neighbouring limit
    m_ui->sld_boid_neighbouringThreshold->setValue(toSliderValue(5, 50, fm->getNeighbouringDistance()));

    //neighbouring fov enablement
    m_ui->fov_GroupBox->setChecked(fm->getEnableFOVNeighbouring());

    //neighbouring fov angle
    m_ui->sld_neighbouring_fov_angle->setValue(fm->getFOVNeighbouringAngle());


    //setup camera zoom control and signal here

    //read camera z value from config file and set it to the slider
    ngl::Camera m_camera = Configuration::initialiseCamera(1.0);
    m_ui->sld_cameraZoom->setValue(-m_camera.getEye().m_z);
    connect(m_ui->sld_cameraZoom, SIGNAL(valueChanged(int)), m_world, SLOT(updateCameraZoom(int)));

}

void MainApp::keyPressEvent(
                               QKeyEvent* _event
                            )
{
    // this method is called every time the main window recives a key event.
    this->setFocus(Qt::ActiveWindowFocusReason);

    switch (_event->key())
    {
        case Qt::Key_Escape : QApplication::exit(EXIT_SUCCESS); break;

        case Qt::Key_M : showFullScreen(); break;
        case Qt::Key_N : showNormal(); break;

        default : break;
    }

    m_world->keyPress(_event);
}

int MainApp::toSliderValue
                        (
                            const float _lowerTrue,
                            const int _lowerSlider,
                            const float _lowerX
                        )
{
    float ratio = (float)_lowerSlider / _lowerTrue;
    return (int)(_lowerX * ratio);
}

float MainApp::fromSliderValue
                        (
                            const float _lowerTrue,
                            const int _lowerSlider,
                            const int _lowerX)
{
    float ratio = (float)_lowerSlider / _lowerTrue;
    return (float)_lowerX / ratio;
}

void MainApp::on_rdo_wall_limiting_hard_toggled(const bool _checked)
{
    //hard limiting selected? disable slider, else enable it
    m_ui->sld_wall_soft_weight->setEnabled(!_checked);

    //update wall limiting type
    m_world->getWall()->setHardWall(_checked);

    //updates world
    m_world->updateGL();
}

void MainApp::setSliderWallSsoftWeightState(const bool _state)
{
    //disable/enable slider according to the type of limiting chosen
    m_ui->sld_wall_soft_weight->setEnabled(_state);
}

void MainApp::on_chk_boid_color_random_toggled(const bool _checked)
{
    //enable/disable spinners if random color is ticked
    setBoiColorSpinnerEnablement(!_checked);

    //update boid random color flag
    m_world->getFlockManager()->setEnableRandomBoidColor(_checked);
}

void MainApp::setBoiColorSpinnerEnablement(const bool _state)
{
    //enable/disable spinners if random color is ticked
    m_ui->dspin_boid_color_r->setEnabled(_state);
    m_ui->dspin_boid_color_g->setEnabled(_state);
    m_ui->dspin_boid_color_b->setEnabled(_state);
}

//World SLOTS
void MainApp::on_cmd_addBoid_clicked()
{
    //add a boid to the current flock
    m_world->getFlockManager()->addNewBoidToFlock();

    //update max of spinner trace neighbours
    m_ui->spin_flock_traceBoidAndNeighbours_boidNo->setMaximum(m_world->getFlockManager()->getNumberOfBoids() - 1);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_cmd_singleStep_clicked()
{
    //single step flock
    m_world->getFlockManager()->moveFlock(m_world->getWall(), m_world->getObstacleManager());

    //redraw world
    m_world->updateGL();
}

void MainApp::on_cmd_refreshSimulation_clicked()
{
    //clears obstacle stat counters
    m_world->getObstacleManager()->clearStatCounters();

    //discard the current flock and create new one from the current settings
    m_world->refreshSimulation(m_ui->chk_2DTopView->isChecked(), m_ui->spin_flock_numberOfBoids->value());

    //update max value of spinner trace neighbours
    m_ui->spin_flock_traceBoidAndNeighbours_boidNo->setMaximum(m_world->getFlockManager()->getNumberOfBoids() - 1);
}

void MainApp::on_traceBoidAndNeighbour_GroupBox_clicked(const bool _checked)
{
    //toggle enable trace on neighbour
    m_world->getFlockManager()->setEnableBoidNeighbourScreening(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_spin_flock_traceBoidAndNeighbours_boidNo_valueChanged(const int _value)
{
    //toggle id of boid under neighbour trace
    m_world->getFlockManager()->setNeighbourUnderScreen(_value);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_chk_rules_normaliseVelocities_clicked(const bool _checked)
{
    //toggle normalise rules
    m_world->getFlockManager()->setNormaliseRules(_checked);
}

void MainApp::on_cohesion_GroupBox_clicked(const bool _checked)
{
    //toggle cohesion rule
    m_world->getFlockManager()->setEnableCohesion(_checked);
}

void MainApp::on_separation_GroupBox_clicked(const bool _checked)
{
    //toggle separation rule
    m_world->getFlockManager()->setEnableSeparation(_checked);
}

void MainApp::on_alignment_GroupBox_clicked(const bool _checked)
{
    //toggle alignment rule
    m_world->getFlockManager()->setEnableAlignment(_checked);
}

void MainApp::on_tendToward_GroupBox_clicked(const bool _checked)
{
    //toggle tend toward rule
    m_world->getFlockManager()->setEnableTendToward(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_externalForce_GroupBox_clicked(const bool _checked)
{
    //toggle external force rule
    m_world->getFlockManager()->setEnableExternalForce(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_obstacle_GroupBox_clicked(const bool _checked)
{
    //toggle obstacle
    m_world->getObstacleManager()->setEnableObstacle(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_chk_rules_obstacle_wireframe_clicked(const bool _checked)
{
    //toggle obstacle wireframe
    m_world->getObstacleManager()->setDrawWireframe(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_chk_rules_obstacle_drawInfluenceSphere_clicked(const bool _checked)
{
    //toggle draw influence sphere
    m_world->getObstacleManager()->setDrawInfluenceSphere(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_obstacleAvoidance_GroupBox_clicked(const bool _checked)
{
    //toggle obstacle avoidance rule
    m_world->getFlockManager()->setEnableObstacleAvoidance(_checked);
}

void MainApp::on_sld_rules_cohesion_weight_valueChanged(const int _value)
{
    //update cohesion weight
    m_world->getFlockManager()->setCohesionWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_rules_separation_weight_valueChanged(const int _value)
{
    //update separation weight
    m_world->getFlockManager()->setSeparationWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_rules_separation_threshold_valueChanged(const int _value)
{
    //update separation distance
    m_world->getFlockManager()->setSeparationDistance(fromSliderValue(2, 20, _value));
}

void MainApp::on_sld_rules_alignment_weight_valueChanged(const int _value)
{
    //update alignment weight
    m_world->getFlockManager()->setAlignmentWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_rules_tendTowards_weight_valueChanged(const int _value)
{
    //update tend toward weight
    m_world->getFlockManager()->setTendTowardWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_rules_externalForce_weight_valueChanged(const int _value)
{
    //update external force weight
    m_world->getFlockManager()->setExternalForceWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_rules_obstacleAvoidance_weight_valueChanged(const int _value)
{
    //update obstacle avoidance weight
    m_world->getFlockManager()->setObstacleAvoidanceWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_dspin_rules_tendTowards_position_x_valueChanged(const double _val)
{
    //update tend towards position
    m_world->getFlockManager()->setTendToward(ngl::Vector(_val, m_world->getFlockManager()->getTendToward().m_y, m_world->getFlockManager()->getTendToward().m_z));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_dspin_rules_tendTowards_position_y_valueChanged(const double _val)
{
    //update tend towards position
    m_world->getFlockManager()->setTendToward(ngl::Vector(m_world->getFlockManager()->getTendToward().m_x, _val, m_world->getFlockManager()->getTendToward().m_z));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_dspin_rules_tendTowards_position_z_valueChanged(const double _val)
{
    //update tend towards position
    m_world->getFlockManager()->setTendToward(ngl::Vector(m_world->getFlockManager()->getTendToward().m_x, m_world->getFlockManager()->getTendToward().m_y, _val));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_dspin_rules_externalForce_force_x_valueChanged(const double _val)
{
    //update external force
    m_world->getFlockManager()->setExternalForce(ngl::Vector(_val, m_world->getFlockManager()->getExternalForce().m_y, m_world->getFlockManager()->getExternalForce().m_z));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_dspin_rules_externalForce_force_y_valueChanged(const double _val)
{
    //update external force
    m_world->getFlockManager()->setExternalForce(ngl::Vector(m_world->getFlockManager()->getExternalForce().m_x, _val, m_world->getFlockManager()->getExternalForce().m_z));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_dspin_rules_externalForce_force_z_valueChanged(const double _val)
{
    //update external force
    m_world->getFlockManager()->setExternalForce(ngl::Vector(m_world->getFlockManager()->getExternalForce().m_x, m_world->getFlockManager()->getExternalForce().m_y, _val));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_wall_GroupBox_clicked(const bool _checked)
{
    //toggle wall limiting
    m_world->getWall()->setEnableWall(_checked);

    //redraw world
    m_world->updateGL();
}

void MainApp::on_spin_wall_dimensions_x_valueChanged(const int _val)
{
    //update wall dimensions
    m_world->getWall()->setBounds(ngl::Vector(_val, m_world->getWall()->getBounds().m_y, m_world->getWall()->getBounds().m_z));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_spin_wall_dimensions_y_valueChanged(const int _val)
{
    //update wall dimensions
    m_world->getWall()->setBounds(ngl::Vector(m_world->getWall()->getBounds().m_x, _val, m_world->getWall()->getBounds().m_z));

    //redraw world
    m_world->updateGL();

}

void MainApp::on_spin_wall_dimensions_z_valueChanged(const int _val)
{
    //update wall dimensions
    m_world->getWall()->setBounds(ngl::Vector(m_world->getWall()->getBounds().m_x, m_world->getWall()->getBounds().m_y, _val));

    //redraw world
    m_world->updateGL();
}

void MainApp::on_sld_wall_soft_weight_valueChanged(const int _value)
{
    //update soft wall weight
    m_world->getWall()->setSoftLimitWeight(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_cmb_boid_model_currentIndexChanged(const int _index)
{
    //get the selected text
    std::string model = m_ui->cmb_boid_model->itemText(_index).toStdString();

    //update boid's model
    m_world->getFlockManager()->setBoidModel(model);

    //redraws world
    m_world->updateGL();
}

void MainApp::on_chk_boid_model_wireframe_clicked(const bool _checked)
{
    //toggle boid wireframe
    m_world->getFlockManager()->setWireframeBoids(_checked);

    //redraws world
    m_world->updateGL();
}

void MainApp::on_chk_boid_drawBoundingSphere_clicked(const bool _checked)
{
    //toggle boid bounding sphere draw
    m_world->getFlockManager()->setDrawBoidBoundingSphere(_checked);

    //redraws world
    m_world->updateGL();
}

void MainApp::on_sld_boid_scale_valueChanged(const int _value)
{
    //update boid scale factor
    m_world->getFlockManager()->setBoidScaleFactor(fromSliderValue(0.5, 5, _value));

    //redraws world
    m_world->updateGL();
}

void MainApp::on_dspin_boid_color_r_valueChanged(const double _val)
{
    //update boid initial red color component
    m_world->getFlockManager()->setBoidColor(ngl::Colour(_val, m_world->getFlockManager()->getBoidColor().m_g, m_world->getFlockManager()->getBoidColor().m_b));
}

void MainApp::on_dspin_boid_color_g_valueChanged(const double _val)
{
    //update boid initial green color component
    m_world->getFlockManager()->setBoidColor(ngl::Colour(m_world->getFlockManager()->getBoidColor().m_r, _val, m_world->getFlockManager()->getBoidColor().m_b));
}

void MainApp::on_dspin_boid_color_b_valueChanged(const double _val)
{
    //update boid initial blue color component
    m_world->getFlockManager()->setBoidColor(ngl::Colour(m_world->getFlockManager()->getBoidColor().m_r, m_world->getFlockManager()->getBoidColor().m_g, _val));
}

void MainApp::on_cmb_boid_shader_currentIndexChanged(const int _index)
{
    //get the selected text
    std::string shader = m_ui->cmb_boid_shader->itemText(_index).toStdString();

    //update boid's shader
    m_world->getFlockManager()->setBoidShader(shader);

    //redraws world
    m_world->updateGL();
}

void MainApp::on_sld_boid_speed_min_valueChanged(const int _value)
{
    //update boid min speed
    m_world->getFlockManager()->setMinSpeed(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_sld_boid_speed_max_valueChanged(const int _value)
{
    //update boid max speed
    m_world->getFlockManager()->setMaxSpeed(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_cmd_autoFlock_clicked()
{
    //toggle auto movement of flock

    //change the name of the command button depending on the state of automove
    m_ui->cmd_autoFlock->setText(!m_world->getFlockManager()->getEnableAutoFlock() ? "AutoMove Stop" : "AutoMove Start");

    //toggle the automove state
    m_world->getFlockManager()->toggleEnableAutoFlock();
}

void MainApp::on_sld_boid_neighbouringThreshold_valueChanged(const int _value)
{
    //update neighbouring threshold
    m_world->getFlockManager()->setNeighbouringDistance(fromSliderValue(5, 50, _value));
}

void MainApp::on_fov_GroupBox_clicked(const bool _checked)
{
    //toggle neighbouring fov policy
    m_world->getFlockManager()->setEnableFOVNeighbouring(_checked);
}

void MainApp::on_sld_neighbouring_fov_angle_valueChanged(const int _value)
{
    //update neighbouring fov angle
    m_world->getFlockManager()->setFOVNeighbouringAngle(_value);
}
